<template>
  <div class="notification-list">
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <h3>通知列表</h3>
          <div>
            <el-button
                type="primary"
                @click="markAllAsRead"
                :disabled="unreadCount === 0"
                :loading="markingAllAsRead"
            >
              全部标为已读
            </el-button>
          </div>
        </div>
      </template>

      <!-- 搜索和筛选 -->
      <div class="search-form-container">
        <div class="search-bar">
          <div class="filter-items">
            <span class="filter-label">通知类型</span>
            <el-select v-model="searchForm.type" placeholder="请选择类型" clearable size="default" class="filter-item type-select">
              <el-option v-if="isAdmin" label="审批通知" value="APPROVAL_REQUIRED" />
              <el-option label="审批结果" value="APPROVAL_RESULT" />
              <el-option label="系统通知" value="SYSTEM_MESSAGE" />
            </el-select>

            <span class="filter-label">状态</span>
            <el-select v-model="searchForm.read" placeholder="请选择状态" clearable size="default" class="filter-item status-select">
              <el-option label="已读" :value="true" />
              <el-option label="未读" :value="false" />
            </el-select>

            <span class="filter-label">日期</span>
            <el-date-picker
                v-model="searchForm.dateRange"
                type="daterange"
                range-separator="至"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
                class="date-picker filter-item"
                size="default"
            />

            <div class="action-buttons">
              <el-button type="primary" @click="handleSearch" size="default">搜索</el-button>
              <el-button @click="resetSearch" size="default">重置</el-button>
            </div>
          </div>
        </div>
      </div>

      <!-- 列表 -->
      <div class="notifications-wrapper">
        <div v-if="notifications.length > 0" class="notifications-container" ref="notificationsContainer" @scroll="handleScroll">
          <div
              v-for="notification in notifications"
              :key="notification.id"
              class="notification-item"
              :class="{ 'notification-unread': !notification.read }"
          >
            <div class="notification-header">
              <div class="notification-type">
                <el-tag :type="getNotificationType(notification.type)" size="small">
                  {{ getNotificationTypeText(notification.type) }}
                </el-tag>
              </div>
              <div class="notification-time">
                {{ formatDateTime(notification.createdAt) }}
              </div>
            </div>

            <div class="notification-content">
              <div class="notification-title">
                <span v-if="!notification.read" class="unread-dot"></span>
                {{ notification.title }}
              </div>
              <div class="notification-message">
                {{ notification.content }}
              </div>
            </div>

            <div class="notification-actions">
              <el-button
                  v-if="notification.referenceId && (notification.type === 'APPROVAL' || notification.type === 'APPROVAL_REQUIRED' || notification.type === 'APPROVAL_RESULT')"
                  link
                  type="primary"
                  @click="viewRelatedItem(notification)"
              >
                查看详情
              </el-button>
              <el-button
                  v-if="!notification.read"
                  link
                  type="info"
                  @click="markAsRead(notification.id)"
              >
                标为已读
              </el-button>
            </div>
          </div>

          <div v-if="loading" class="loading-more">
            <el-icon class="is-loading"><Loading /></el-icon>
            <span>加载中...</span>
          </div>

          <div v-if="noMore && notifications.length > 0" class="no-more">
            没有更多通知了
          </div>
        </div>

        <el-empty v-if="notifications.length === 0" description="暂无通知" />
      </div>
    </el-card>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
import { getNotifications, markNotificationAsRead, markAllNotificationsAsRead } from '@/api/notification'
import { getPurchaseRequestById } from '@/api/purchase-request'
import { formatDateTime } from '@/utils/format'
import { useNotificationStore } from '@/store/notification'
import { useAuthStore } from '@/store/auth'
import { Loading } from '@element-plus/icons-vue'

const router = useRouter()
const notificationStore = useNotificationStore()
const authStore = useAuthStore()
const loading = ref(false)
const markingAllAsRead = ref(false)
const notifications = ref([])
const noMore = ref(false)
const notificationsContainer = ref(null)

// 判断是否是管理员
// 这段代码的作用是什么？	定义一个计算属性 isAdmin，用于判断当前登录用户是否具有管理员角色（ROLE_ADMIN）。
// computed 是什么？	Vue 3 提供的一个响应式 API，用于定义计算属性，它的值会根据依赖的响应式数据自动计算并缓存。
// authStore.user 和 authStore.user.roles 是什么？	分别表示当前登录用户的信息和用户的角色列表，通常从 Pinia 的 authStore 中获取。
// 为什么要用链式条件判断？	为了安全地访问嵌套属性，避免因用户未登录或角色信息缺失而导致的运行时错误（如 TypeError）。
// 计算属性的特点是什么？	响应式依赖、缓存机制、只读性。
// 如何在组件中使用 isAdmin？	可以在模板中通过 v-if="isAdmin" 实现条件渲染，或在逻辑中通过 isAdmin.value 进行权限控制。
const isAdmin = computed(() => {
  return authStore.user && authStore.user.roles && authStore.user.roles.includes('ROLE_ADMIN')
})


// 搜索表单
// 这段代码的作用是什么？	定义一个响应式对象 searchForm，用于存储和管理表单数据或搜索条件（如类型、已读状态、日期范围）。
// reactive 是什么？	Vue 3 提供的一个响应式 API，用于创建响应式对象，使对象的属性变化能够被 Vue 自动追踪。
// searchForm 包含哪些属性？	type（类型）、read（已读状态）、dateRange（日期范围），初始值分别为空字符串、null 和空数组。
// 为什么要用 reactive？	为了方便地管理多个相关联的表单字段或搜索条件，并让它们具有响应式特性，从而实现动态交互和数据绑定。
// 如何在组件中使用 searchForm？	可以在模板中通过 v-model 绑定表单输入，在逻辑中访问或修改它的属性值，或通过 watch 监听它的变化。
const searchForm = reactive({
  type: '',
  read: null,
  dateRange: []
})


// 分页
const pagination = reactive({
  page: 0,
  size: 20,
  total: 0
})

// 计算未读通知数量，根据 notifications 中的数据，实时计算出其中未读通知（read 为 false）的数量，并将这个数量作为响应式的值暴露出来
const unreadCount = computed(() => {
  return notifications.value.filter(n => !n.read).length//从 notifications 中筛选出所有未读的通知（read 为 false 的通知），生成一个新的数组。
})

// 获取通知列表，它的主要作用是从后端获取通知列表数据，并根据不同的场景（首次加载或加载更多）更新前端的状态。这段代码是一个典型的数据获取与状态管理的实现，常用于分页加载、搜索筛选等场景。
// 这段代码的作用是什么？	定义一个异步函数 fetchNotifications，用于从后端获取通知列表数据，并根据不同场景（首次加载或加载更多）更新前端状态。
// 函数参数 isLoadMore 的作用是什么？	区分当前是首次加载通知列表还是加载更多通知（分页加载）。
// 如何防止重复请求？	通过 loading.value 判断是否正在加载数据，如果正在加载则直接退出函数。
// 请求参数是如何构建的？	根据分页信息 pagination 和搜索表单 searchForm 动态生成请求参数。
// 如何处理响应数据？	提取通知列表数据，并根据 isLoadMore 决定是追加数据还是替换数据。
// 如何判断是否还有更多数据？	根据当前页的数据条数和总数据条数计算是否还有更多数据可加载。
// 错误处理是如何实现的？	使用 try...catch 捕获错误，并向用户展示错误提示，同时清理加载状态。
// finally 的作用是什么？	确保无论请求成功还是失败，都会正确更新加载状态。
const fetchNotifications = async (isLoadMore = false) => {
  if (loading.value) return

  loading.value = true
  try {
    const params = {
      page: pagination.page,
      size: pagination.size,
      type: searchForm.type || undefined,
      read: searchForm.read !== null ? searchForm.read : undefined,
      startDate: searchForm.dateRange && searchForm.dateRange[0] ? searchForm.dateRange[0] : undefined,
      endDate: searchForm.dateRange && searchForm.dateRange[1] ? searchForm.dateRange[1] : undefined
    }//定义了一个 params 对象，用于存储向后端 API 发送请求时的查询参数；

    console.log('获取通知列表，参数:', params)//打印请求参数，方便调试
    const response = await getNotifications(params)
    console.log('通知列表响应:', response)

    const newNotifications = response.content || []//从响应数据 response 中提取实际的通知列表数据，使用空数组 [] 作为默认值，避免后续代码出错。

    if (isLoadMore) {
      // 追加新数据
      notifications.value = [...notifications.value, ...newNotifications]
    } else {
      // 替换数据
      notifications.value = newNotifications
    }

    pagination.total = response.totalElements || 0

    // 判断是否还有更多数据，定义了一个响应式变量 noMore.value，用于表示是否已经加载完所有数据
    noMore.value = newNotifications.length < pagination.size ||
        (pagination.page + 1) * pagination.size >= pagination.total

    // 更新通知计数
    notificationStore.fetchUnreadCount()
  } catch (error) {
    console.error('获取通知列表失败:', error)
    ElMessage.error('获取通知列表失败: ' + (error.message || '未知错误'))
    if (!isLoadMore) {
      notifications.value = []
    }
  } finally {
    loading.value = false
  }
}

// 加载更多
const loadMore = () => {
  if (loading.value || noMore.value) return

  pagination.page++
  fetchNotifications(true)
}

// 搜索，它的主要作用是处理用户触发的搜索操作，并在搜索时重置分页状态，然后重新获取通知列表数据。
//这段代码的作用是什么？	定义一个函数 handleSearch，用于在用户触发搜索操作时重置分页状态和“是否还有更多数据”的状态，然后重新获取符合搜索条件的通知列表数据。
// pagination.page = 0 的作用是什么？	重置当前页码为第一页（或第 0 页），确保搜索时从最开始的数据加载，而不是继续加载之前页码的数据。
// noMore.value = false 的作用是什么？	重置“是否还有更多数据”的状态，确保搜索后可以继续加载更多数据（如果有的话）。
// fetchNotifications() 的作用是什么？	发起 API 请求获取通知列表数据，并根据当前的搜索条件和分页状态更新前端的状态。
// handleSearch 通常与哪些功能配合使用？	与分页功能（pagination）、搜索表单（searchForm）、“是否还有更多数据”状态（noMore）等配合使用，实现完整的搜索功能。
// handleSearch 如何与用户交互绑定？	可以绑定到“搜索”按钮的点击事件或表单的提交事件上，响应用户的搜索操作。
const handleSearch = () => {
  // pagination.page = 0//表示从头开始加载数据。
  noMore.value = false
  fetchNotifications()
}

// 重置搜索
const resetSearch = () => {
  searchForm.type = ''
  searchForm.read = null
  searchForm.dateRange = []
  pagination.page = 0
  noMore.value = false
  fetchNotifications()
}

// 查看相关项目
//这段代码的作用是什么？	定义一个异步函数 viewRelatedItem，根据通知的类型执行不同的操作（如跳转页面、标记已读等）。
// 函数参数 notification 是什么？	表示当前用户点击的通知对象，包含通知的 id、type、read 状态、referenceId 等属性。
// 如何处理审批相关通知？	检查 referenceId 是否存在，如果存在则跳转到采购申请详情页，并标记通知为已读；如果不存在则提示用户。
// 如何处理系统通知？	只标记通知为已读，不跳转页面，并向用户展示提示信息。
// 如何处理未知类型的通知？	向用户展示警告信息，提示通知类型未知。
// 异常情况是如何处理的？	在调用 API 或跳转页面时捕获可能的错误，并向用户展示友好的提示信息。
const viewRelatedItem = async (notification) => {
  console.log('查看通知详情:', notification);

  if (notification.type === 'APPROVAL' || notification.type === 'APPROVAL_REQUIRED' || notification.type === 'APPROVAL_RESULT') {
    if (notification.referenceId) {
      console.log('跳转到采购申请详情页:', notification.referenceId);

      try {
        // 先检查采购申请是否存在
        const response = await getPurchaseRequestById(notification.referenceId);//通过采购申请的 ID 从后端获取采购申请的详细信息；
        if (response) {
          router.push(`/purchase-requests/${notification.referenceId}`);//使用 Vue Router 的 router.push() 方法跳转到采购申请的详情页；${notification.referenceId} 是动态插入的采购申请 ID。

          // 如果未读，标记为已读
          if (!notification.read) {
            markAsRead(notification.id);
          }
        }
      } catch (error) {
        console.error('获取采购申请详情失败:', error);
        ElMessage.warning('申请已被删除');

        // 即使申请已删除，也标记通知为已读
        if (!notification.read) {
          markAsRead(notification.id);
        }
      }
    } else {
      console.error('通知缺少引用ID:', notification);
      ElMessage.warning('无法查看详情：通知缺少引用ID');
    }
  } else if (notification.type === 'SYSTEM' || notification.type === 'SYSTEM_MESSAGE') {
    // 系统通知没有详情页，只标记为已读
    if (!notification.read) {
      markAsRead(notification.id);
    }
    ElMessage.info('系统通知无需跳转');
  } else {
    console.log('未知通知类型:', notification.type);
    ElMessage.warning(`未知通知类型: ${notification.type}`);
  }
}

// 标记为已读
//异步函数async，返回promise， 使用 await 关键字等待 Promise 完成
const markAsRead = async (id) => {
  try {
    await markNotificationAsRead(id)//使用了 await 关键字，意味着它会等待这个函数执行完成后再继续往下执行。
    // markNotificationAsRead（封装好的)用于向服务器发送请求，将指定 id 的通知标记为已读。
    //这是个异步函数

    // 更新本地状态
    const index = notifications.value.findIndex(n => n.id === id)
    if (index !== -1) {
      notifications.value[index].read = true
    }

    // 更新通知计数
    notificationStore.fetchUnreadCount()
  } catch (error) {
    console.error('标记通知为已读失败:', error)
    ElMessage.error('标记通知为已读失败')
  }
}

// 全部标为已读，它的主要作用是将用户的所有通知标记为已读。这个功能在通知系统中非常常见，通常通过一个“全部标为已读”的按钮触发。
// 这段代码的作用是什么？	定义一个异步函数 markAllAsRead，用于将用户的所有通知标记为已读，并更新本地状态和全局未读通知计数。
// 函数的主要逻辑是什么？	调用后端 API 标记所有通知为已读，更新本地通知状态，向用户展示成功提示，更新未读通知计数，并处理可能的异常情况。
// markingAllAsRead 的作用是什么？	表示“是否正在将所有通知标记为已读”的操作状态，用于控制加载状态和按钮的禁用状态。
// 如何更新本地通知状态？	遍历 notifications.value，将每条通知的 read 属性设置为 true。
// 如何保证未读通知计数的实时性？	调用 notificationStore.fetchUnreadCount()，从后端获取最新的未读通知数量并更新全局状态。
// 异常情况是如何处理的？	使用 try...catch 捕获 API 请求过程中可能发生的错误，并向用户展示友好的提示信息。
// 如何在组件中使用 markAllAsRead？	绑定到“全部标为已读”按钮的点击事件，并通过 markingAllAsRead 控制按钮的禁用状态和文本内容。
const markAllAsRead = async () => {
  markingAllAsRead.value = true
  try {
    await markAllNotificationsAsRead()

    // 更新本地状态
    notifications.value.forEach(notification => {
      notification.read = true
    })

    ElMessage.success('已将所有通知标记为已读')

    // 更新通知计数
    notificationStore.fetchUnreadCount()
  } catch (error) {
    console.error('标记所有通知为已读失败:', error)
    ElMessage.error('标记所有通知为已读失败')
  } finally {
    markingAllAsRead.value = false//在 finally 块中，无论 API 请求成功还是失败，都会将 markingAllAsRead.value 设置为 false；这是为了确保加载状态的正确更新，避免因异常情况导致加载状态卡住。
  }
}

// 获取通知类型
const getNotificationType = (type) => {
  switch (type) {
    case 'APPROVAL': return 'success'
    case 'APPROVAL_REQUIRED': return 'success'
    case 'APPROVAL_RESULT': return 'success'
    case 'SYSTEM': return 'info'
    case 'SYSTEM_MESSAGE': return 'info'
    default: return 'info'
  }
}

// 获取通知类型文本
const getNotificationTypeText = (type) => {
  switch (type) {
    case 'APPROVAL': return '审批通知'
    case 'APPROVAL_REQUIRED': return '审批通知'
    case 'APPROVAL_RESULT': return '审批通知'
    case 'SYSTEM': return '系统通知'
    case 'SYSTEM_MESSAGE': return '系统通知'
    default: return '未知'
  }
}

// 处理滚动事件
const handleScroll = (e) => {
  const { scrollTop, scrollHeight, clientHeight } = e.target
  // 当滚动到距离底部50px以内时，加载更多数据
  if (scrollHeight - scrollTop - clientHeight < 50 && !loading.value && !noMore.value) {
    loadMore()
  }
}

onMounted(() => {
  fetchNotifications()
})
</script>

<style scoped>
.notification-list {
  padding: 20px;
  height: calc(100vh - 120px);
  overflow: hidden;
}

.box-card {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.search-form-container {
  margin-bottom: 20px;
  overflow-x: auto;
}

.search-bar {
  display: flex;
  align-items: center;
}

.filter-items {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  width: 100%;
}

.filter-label {
  margin-right: 8px;
  white-space: nowrap;
}

.filter-item {
  margin-right: 16px;
}

.action-buttons {
  display: flex;
  white-space: nowrap;
}

.action-buttons .el-button {
  margin-left: 8px;
}

.action-buttons .el-button:first-child {
  margin-left: 0;
}

.date-picker {
  width: 220px;
}

.notifications-wrapper {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.notifications-container {
  flex: 1;
  overflow-y: auto;
  max-height: calc(100vh - 300px);
}

.notification-item {
  padding: 15px;
  border-bottom: 1px solid #ebeef5;
  transition: background-color 0.3s;
}

.notification-item:hover {
  background-color: #f5f7fa;
}

.notification-unread {
  background-color: #f0f9ff;
}

.notification-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}

.notification-content {
  margin-bottom: 10px;
}

.notification-title {
  font-weight: bold;
  margin-bottom: 5px;
  display: flex;
  align-items: center;
}

.unread-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: #409eff;
  margin-right: 5px;
  display: inline-block;
}

.notification-message {
  color: #606266;
}

.notification-actions {
  display: flex;
  justify-content: flex-end;
}

.loading-more {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 15px 0;
  color: #909399;
}

.loading-more .el-icon {
  margin-right: 5px;
}

.no-more {
  text-align: center;
  color: #909399;
  padding: 15px 0;
  font-size: 14px;
}

.type-select {
  width: 120px;
}

.status-select {
  width: 120px;
}
</style>